﻿// <copyright file="GestureService.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Services
{
    #region

    using System;

    using KinectExtensions.Nui;

    using Microsoft.Research.Kinect.Nui;

    #endregion

    public class JointGestureService
    {
        #region Constants and Fields

        private readonly JointID jointId;

        private Vector? lastReferencePoint;

        private DateTime lastReferencePointDateTime;

        private float segmentSize = 0.01f;

        private float segmentZSize = 0.01f;

        #endregion

        #region Constructors and Destructors

        public JointGestureService(JointID id)
        {
            this.jointId = id;
        }

        #endregion

        #region Public Events

        public event EventHandler<GestureSegmentEventArgs> GestureSegmentDetected;

        #endregion

        #region Public Properties

        public JointID JointId
        {
            get
            {
                return this.jointId;
            }
        }

        #endregion

        #region Public Methods

        public void AddPosition(SkeletonData data)
        {
            Joint originalJoint = data.Joints[this.JointId];

            if (originalJoint.TrackingState != JointTrackingState.Tracked)
            {
                return;
            }

            DateTime dt = DateTime.Now;
            Joint head = data.Joints[JointID.Head];
            Joint hipCenter = data.Joints[JointID.HipCenter];
            float referenceSize = head.Position.Y - hipCenter.Position.Y;

            Joint joint = originalJoint.Move(x => x / referenceSize, y => y / referenceSize / 1.3333f);
            float squaredXYsize;
            float squaredZsize;
            if (this.lastReferencePoint == null || (dt - this.lastReferencePointDateTime).TotalMilliseconds > 500)
            {
                this.lastReferencePoint = joint.Position;
                this.lastReferencePointDateTime = dt;
            }
            else
            {
                squaredXYsize = joint.Position.SquaredXYSize(this.lastReferencePoint.Value);
                squaredZsize = joint.Position.SquaredZ(this.lastReferencePoint.Value);
                if (squaredXYsize >= this.segmentSize || squaredZsize >= this.segmentZSize)
                {
                    XyGestureDirection xy = this.GetXyDirection(joint.Position, this.lastReferencePoint.Value, squaredXYsize);
                    ZGestureDirection z = this.GetZDirection(joint.Position, this.lastReferencePoint.Value, squaredZsize);
                    TimeSpan duration = (dt - this.lastReferencePointDateTime);
                    this.lastReferencePoint = joint.Position;
                    this.lastReferencePointDateTime = dt;
                    this.OnGestureSegmentDetected(xy, z, squaredXYsize, squaredZsize, duration, joint, data, referenceSize);
                }
            }
        }

        #endregion

        #region Methods

        protected virtual void OnGestureSegmentDetected(XyGestureDirection xy, ZGestureDirection z, float squareLengthXY, float squareLengthZ, TimeSpan duration, Joint joint, SkeletonData data, float referenceSize)
        {
            if (this.GestureSegmentDetected != null)
            {
                this.GestureSegmentDetected(this, new GestureSegmentEventArgs(xy, z, squareLengthXY, squareLengthZ, duration, joint, data, referenceSize));
            }
        }

        private XyGestureDirection GetXyDirection(Vector vector, Vector lastReferencePoint, float length)
        {
            if (length < this.segmentSize)
            {
                return XyGestureDirection.None;
            }

            float dx = vector.X - lastReferencePoint.X;
            float dy = vector.Y - lastReferencePoint.Y;

            if (Math.Abs(dy) < 0.03f)
            {
                return dx < 0 ? XyGestureDirection.W : XyGestureDirection.E;
            }

            if (Math.Abs(dx) < 0.03f)
            {
                return dy < 0 ? XyGestureDirection.S : XyGestureDirection.N;
            }

            if (dx < 0)
            {
                return dy < 0 ? XyGestureDirection.SW : XyGestureDirection.NW;
            }
            return dy < 0 ? XyGestureDirection.SE : XyGestureDirection.NE;
        }

        private ZGestureDirection GetZDirection(Vector vector, Vector lastReferencePoint, float length)
        {
            if (length < this.segmentZSize)
            {
                return ZGestureDirection.None;
            }

            float dz = lastReferencePoint.Z - vector.Z;
            return dz < 0 ? ZGestureDirection.Front : ZGestureDirection.Back;
        }

        #endregion
    }
}